'''
controller.py

Copyright 2009 Xavier Mendez Navarro aka Javi

This file is part of pysqlin

pysqlin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.

pysqlin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with pysqlin; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
'''

import logging
from ConfigParser import NoOptionError
from ConfigParser import NoSectionError
import os

from framework.settings import Settings
#from framework.queries import Queries
from framework.queries import Database
from framework.queries import Query
from framework.myexceptions import MyException
from framework.sqlinjection import SQLInjection
from framework.mymodel import InjectionData
from framework.roles import Observer
from framework.facade import Facade
import framework.myevents



class BaseController(Observer):
    def __init__(self, view):
	self.facade = Facade()
	self.view = view
	self.__logger = logging.getLogger("framework.BaseController")

	self.last_log = []
	self.plugin_name = ""

    # ------------------------------------------------
    # session functions
    # ------------------------------------------------
    def print_status(self, msg):
	raise MyException('Must define this method')

    def print_error(self, msg):
	raise MyException('Must define this method')

    def print_msg(self, msg):
	raise MyException('Must define this method')

    def print_char(self, pos, char):
	raise MyException('Must define this method')

class ConsoleController(BaseController):
    def __init__(self, view):
    	BaseController.__init__(self, view)

	self.__logger = logging.getLogger("framework.console_controller")

	self.subscribe(self.about, 'COMMAND root.about')

	# section: global settings
	self.subscribe(self.evt_get_sections, 'PARAMETER root.setg.section')
	self.subscribe(self.evt_get_sections, 'PARAMETER root.showg.section')
	self.subscribe(self.evt_get_entries, 'PARAMETER root.setg.parameter.' + Settings.SEC_GRL, True)
	self.subscribe(self.evt_get_entries, 'PARAMETER root.setg.parameter.' + Settings.SEC_HTTP, True)
	self.subscribe(self.evt_get_entries, 'PARAMETER root.setg.parameter.' + Settings.SEC_PERF, True)
	self.subscribe(self.evt_get_entries, 'PARAMETER root.showg.parameter.' + Settings.SEC_GRL, True)
	self.subscribe(self.evt_get_entries, 'PARAMETER root.showg.parameter.' + Settings.SEC_HTTP, True)
	self.subscribe(self.evt_get_entries, 'PARAMETER root.showg.parameter.' + Settings.SEC_PERF, True)
	self.subscribe(self.comm_set_entry, 'COMMAND root.setg')
	self.subscribe(self.comm_show_entries, 'COMMAND root.showg')
	self.subscribe(self.evt_get_glob_entry, 'PARAMETER root.setg.value.' + Settings.DEBUG_LEVEL, True)
	self.subscribe(self.evt_get_glob_entry, 'PARAMETER root.setg.value.' + Settings.AUTO_SESSION, True)
	self.subscribe(self.evt_get_glob_entry, 'PARAMETER root.setg.value.' + Settings.AUTO_SSETT, True)

	# section: injection parameters
	self.subscribe(self.evt_get_info, 'PARAMETER root.info.item_name.plugin', True)
	self.subscribe(self.evt_get_info, 'PARAMETER root.info.item_name.injector', True)
	self.subscribe(self.evt_get_info, 'PARAMETER root.info.item_name.filter', True)
	self.subscribe(self.comm_info, 'COMMAND root.info')

	self.subscribe(self.evt_get_plugins, 'PARAMETER root.use.plugin_name')
	self.subscribe(self.comm_set_plugin, 'COMMAND root.use')

	self.subscribe(self.evt_get_injentries, 'PARAMETER root.set.parameter')
	self.subscribe(self.evt_get_injentries, 'PARAMETER root.show.parameter')
	self.subscribe(self.comm_set_injentry, 'COMMAND root.set')
	self.subscribe(self.comm_show_injentries, 'COMMAND root.show')
	self.subscribe(self.evt_get_entry, 'PARAMETER root.set.value.' + InjectionData.DATABASE, True)
	self.subscribe(self.evt_get_entry, 'PARAMETER root.set.value.' + InjectionData.ERROR_MODE, True)
	self.subscribe(self.evt_get_entry, 'PARAMETER root.set.value.' + InjectionData.AUTH_METHOD, True)
	self.subscribe(self.evt_get_entry, 'PARAMETER root.set.value.' + InjectionData.INJECTOR, True)

	## section: injection query strings
	#self.subscribe(self.evt_get_queries, 'PARAMETER root.set_query.injection_name')
	#self.subscribe(self.evt_get_queries, 'PARAMETER root.show_query.injection_name')
	#self.subscribe(self.comm_show_queries, 'COMMAND root.show_query')
	#self.subscribe(self.comm_set_queries, 'COMMAND root.set_query')

	# info gathered (KnowledgeBase)
	self.subscribe(self.comm_get_kb_data, 'COMMAND root.kbase')
	self.subscribe(self.evt_get_kb_keys, 'PARAMETER root.kbase.key')
	self.subscribe(self.evt_get_kbase, 'KBASE root')


	# debug
	self.subscribe(self.comm_debug_list, 'COMMAND root.debug.list')
	self.subscribe(self.comm_debug_show, 'COMMAND root.debug.show')

	# app exit
	self.subscribe(self.on_exit, 'COMMAND root.exit')
	self.subscribe(self.on_change_section, 'COMMAND root.section')

	# session section
	self.subscribe(self.evt_get_files, 'PARAMETER root.session.value.load', True)
	self.subscribe(self.evt_get_files, 'PARAMETER root.session.value.save', True)
	self.subscribe(self.evt_get_ses_ids, 'PARAMETER root.session.value.remove', True)
	self.subscribe(self.evt_get_ses_ids, 'PARAMETER root.session.value.interact', True)
	self.subscribe(self.comm_session_file, 'COMMAND root.session')

	# filters
	self.subscribe(self.comm_filter, 'COMMAND root.filter')
	#self.subscribe(self.comm_filters_test, 'COMMAND root.filters.test')
	self.subscribe(self.evt_get_filters, 'PARAMETER root.filter.filter_name.enable', True)
	self.subscribe(self.evt_get_filters, 'PARAMETER root.filter.filter_name.disable', True)

	# debug
	self.subscribe(self.comm_error_string, 'COMMAND root.set_error_string')

    # ------------------------------------------------
    # update view events
    # ------------------------------------------------
    def print_status(self, msg):
	self.view.output("[*] " + msg)

    def print_error(self, msg):
	self.view.output("[!] " + msg)

    def print_msg(self, msg):
	self.view.output(msg)

    def print_char(self, pos, char):
	if pos is None:
	    self.view.clear_line()
	else:
	    self.view.print_char(pos, char)

    # ------------------------------------------------
    # error string commands
    # ------------------------------------------------
    def comm_error_string(self, **event):
	data = Facade().get_session().inj_data

	sqli = SQLInjection.from_error_string(data)

	data[InjectionData.ERROR_STRING] = sqli.injection_data[InjectionData.ERROR_STRING]
	data[InjectionData.ERROR_MODE] = sqli.injection_data[InjectionData.ERROR_MODE]

    # ------------------------------------------------
    # filters section commands
    # ------------------------------------------------
    def comm_filter(self, **event):
	operation = event['parameters']['operation']
	filter_name = event['parameters']['filter_name']

	if operation == 'list':
	    db = Facade().get_session().inj_data[InjectionData.DATABASE]
	    self.print_msg('* Order of precedence is lowest priority number on DB_QUERY and then on COOKIE, POST_DATA and URL.')
	    self.view.table_print(Facade().get_filters().get_list(db))
	elif operation == 'enable':
	    if filter_name == 'all':
		Facade().get_filters().enable_all()
		return

	    try:
		Facade().get_filters().set_state(filter_name, True)
	    except KeyError, error:
		return 'No such filter'
	elif operation == 'disable':
	    filter_name = event['parameters']['filter_name']

	    if filter_name == 'all':
		Facade().get_filters().disable_all()
		return

	    try:
		Facade().get_filters().set_state(filter_name, False)
	    except KeyError, error:
		return 'No such filter'
	else:
	    return "Invalid option"

    def comm_filters_test(self, **event):
	str = event['parameters']['string']
	filter_name = event['parameters']['filter_name']

	try:
	    self.print_msg(
		Facade().get_filters().test_filter(filter_name, str))
	except KeyError, error:
	    return 'No such filter'
	except Exception, error:
	    return 'Filter throwed an unexpected error'

    def evt_get_filters(self, **event):
	db = Facade().get_session().inj_data[InjectionData.DATABASE]
	l = Facade().get_filters().names(db)

	if event['cmd'] != 'test':
	    l.append('all')

	return l

    # ------------------------------------------------
    # plugins commands
    # ------------------------------------------------
    def evt_get_plugins(self, **event):
	return Facade().get_plugins().names()

    def comm_set_plugin(self, **event):
	try:
	    plugin_name = event['parameters']['plugin_name']

	    plugin = Facade().get_plugins().get(plugin_name)
	    Facade().load_plugin(plugin)
	    plugin.init()
	    self.plugin_name = plugin.name()
	    self.view.subprompt = "(%s)" % plugin.name()
	except Exception, e:
	    self.__logger.error('comm_set_plugin, exception. %s ' % e)
	    return 'Error loading plugin, exception: %s ' %  e

    # ------------------------------------------------
    # info commands
    # ------------------------------------------------
    def evt_get_info(self, **event):
	if event['current_val'] == 'plugin':
	    return Facade().get_plugins().names()
	if event['current_val'] == 'injector':
	    return Facade().get_injectors().names()
	if event['current_val'] == 'filter':
	    return Facade().get_filters().names()

    def comm_info(self, **event):
	operation = event['parameters']['operation']
	item_name = event['parameters']['item_name']

	if operation == 'plugin':
	    try:
		plugin = Facade().get_plugins().get(item_name)
		self.print_msg('\nName: %s\n\nDescription: %s\n' % (plugin.name(), plugin.description()))
	    except Exception, e:
		self.__logger.error('comm_info_plugin, exception. %s ' % e)
		return 'Error loading plugin, exception: %s ' %  e
	elif operation == 'injector':
	    try:
		injector = Facade().get_injectors().get(item_name)
		self.print_msg('\nName: %s\n\nDescription: %s\n' % (injector.name(), injector.description()))
	    except Exception, e:
		self.__logger.error('comm_info_plugin, exception. %s ' % e)
		return 'Error loading plugin, exception: %s ' %  e
	    pass
	elif operation == 'filter':
	    try:
		filter = Facade().get_filters().get(item_name)
		self.print_msg('\nName: %s\n\nDescription: %s\n' % (filter.name(), filter.summary()))
	    except Exception, e:
		self.__logger.error('comm_info_plugin, exception. %s ' % e)
		return 'Error loading plugin, exception: %s ' %  e

    # ------------------------------------------------
    # debug section commands
    # ------------------------------------------------
    def comm_debug_show(self, **event):
	try:
	    index = int(event['parameters']['number'])
	    req = self.last_log[index]
	except ValueError:
	    return 'Malformed request number'
	except IndexError:
	    return 'Unknown request number'

	which = event['parameters']['which']
	
	if which == 'request':
	    if req: self.print_msg(req.get_raw_req())
	elif which == 'response':
	    if req and req.has_response(): self.print_msg(req.get_raw_resp())

    def comm_debug_list(self, **event):
	for num, req in enumerate(self.last_log):
	    self.print_msg("%d. %s" % (num, str(req)))

    # ------------------------------------------------
    # session section commands
    # ------------------------------------------------
    def evt_get_ses_ids(self, **event):
	return Facade().get_session_ids()

    def evt_get_files(self, **event):
	self.__logger.debug('evt_get_files. START, event: %s', event)

	return filter(lambda x: x.endswith('.pkl'), os.listdir(Settings().path_to_program_dir()))

    def comm_session_file(self, **event):
	operation = event['parameters']['operation']
	value = event['parameters']['value']

	if operation == 'load':
	    try:
		self.facade.load_session(os.path.join(Settings().path_to_program_dir(), value))
	    except MyException, e:
		return "Exception: %s" % e

	    self.print_status('Loaded %s' % value)
	elif operation == 'save':
	    if not value.endswith('.pkl'): value = value + ".pkl"
	    try:
		self.facade.save_session(os.path.join(Settings().path_to_program_dir(), value))
	    except MyException, e:
		return "Exception: %s" % e

	    self.print_status('Saved %s' % value)
	elif operation == 'list':
	    self.view.table_print([['No.', 'URL', 'DB']] + Facade().list_sessions())
	elif operation == 'interact':
	    try:
		Facade().interact_session(int(value))
	    except ValueError:
		return "Invalid session identifier"
	    except MyException:
		return "Unknown session identifier!"
	elif operation == 'new':
	    Facade().new_session()
	elif operation == 'remove':
	    try:
		Facade().remove_session(int(value))
	    except ValueError:
		return "Invalid session identifier"
	    except MyException:
		return "Unknown session identifier!"
	elif operation == 'clone':
	    Facade().clone_session()
	else:
	    return "Invalid option"

    # ------------------------------------------------
    # root section commands
    # ------------------------------------------------
    def on_change_section(self, **event):
	section = event['section']

	if section == 'root' and self.plugin_name:
	    self.view.subprompt = "(%s)" % self.plugin_name

    def on_exit(self, **event):
	self.__logger.debug('on_exit. START')

	if Settings().get(Settings.SEC_GRL, Settings.AUTO_SSETT) == 'True':
	    Settings().save()

	if Settings().get(Settings.SEC_GRL, Settings.AUTO_SESSION) == 'True':
	    self.facade.save_session()

    def about(self, **event):
	self.__logger.debug('START about, param: %s', event)

	self.print_msg('''
			    _  _      
	 ___  _ _  ___ ___ | |<_>._ _ 
	| . \| | |<_-</ . || || || ' |
	|  _/`_. |/__/\_  ||_||_||_|_|
	|_|  <___'      |_|  

	javi (xavi.mendez at gmail.com)

	''')

    # ------------------------------------------------
    # knowledge base events and functions
    # ------------------------------------------------
    def comm_get_kb_data(self, **event):
	self.__logger.debug('comm_get_kb_data. START event=%s', event)

	try:
	    kb = Facade().get_session().kbase.get(event['parameters']['key'])
	except KeyError:
	    return "Unknown knowledge base key."

	for i in kb:
	    self.print_msg(i)

    def evt_get_kb_keys(self, **event):
	self.__logger.debug('evt_get_kb_keys. START event=%s', event)

	return Facade().get_session().kbase.keys()

    def evt_get_kbase(self, **event):
	self.__logger.debug('evt_get_kbase. START event=%s', event)

	try:
	    return Facade().get_session().kbase.get(event['key'])
	except KeyError:
	    return []

    # ------------------------------------------------
    # main settings commands and parameters events
    # ------------------------------------------------
    def evt_get_sections(self, **event):
	l = Settings().get_sections()
	if event['cmd'] == 'showg': l.append('all')

	return l

    def evt_get_entries(self, **event):
	self.__logger.debug('START evt_get_entries, param: %s', event)

	section = event['parameters']['section']

	l = Settings().get_options(section)
	if event['cmd'] == 'showg': l.append('all')

	return l

    def comm_show_entries(self, **event):
	self.__logger.debug('START comm_show_entries, param: %s', event)

	param = event['parameters']['parameter']
	section = event['parameters']['section']

	if section == 'all':
	    sett = Settings().get_all()
	    for section in Settings().get_sections():
		for option, value in sett[section]:
		    self.print_msg("%s = %s" % (option, value))
	else:
	    if param == 'all':
		sett = Settings().get_all()
		try:
		    for option, value in sett[section]:
			self.print_msg("%s = %s" % (option, value))
		except KeyError:
		    return "No section named %s, check configuration files" % (section)
	    else:
		try:
		    self.print_msg(Settings().get(section, param))
		except NoSectionError:
		    return "No section named %s, check configuration files" % (section)
		except NoOptionError:
		    return "No setting named %s, check configuration files" % (param)

    def comm_set_entry(self, **event):
	self.__logger.debug('START comm_set_entry, param: %s', event)

	param = event['parameters']['parameter']
	value = event['parameters']['value']
	section = event['parameters']['section']

	try:
	    Settings().set(section, param, value)
	except NoSectionError:
	    return "No section named %s, check configuration files" % (section)

    def evt_get_glob_entry(self, **event):
	self.__logger.debug('START evt_get_entry, param: %s', event)

	section = Settings.SEC_GRL

	l = []
	if event['current_val'] == Settings.DEBUG_LEVEL and section == Settings.SEC_GRL:
	    l = [Settings.DEBUG_LEVEL_DEBUG, Settings.DEBUG_LEVEL_INFO, 
		Settings.DEBUG_LEVEL_WARNING, Settings.DEBUG_LEVEL_ERROR,
		Settings.DEBUG_LEVEL_CRITICAL]
	if event['current_val'] == Settings.AUTO_SSETT or event['current_val'] == Settings.AUTO_SESSION:
	    l = [Settings.AUTO_TRUE, Settings.AUTO_FALSE]

	return l

    def evt_get_entry(self, **event):
	self.__logger.debug('START evt_get_entry, param: %s', event)

	l = []
	if event['current_val'] == InjectionData.DATABASE:
	    l = Database.db_names()
	elif event['current_val'] == InjectionData.ERROR_MODE:
	    l = [InjectionData.ERROR_MODE_POSITIVE, InjectionData.ERROR_MODE_NEGATIVE]
	elif event['current_val'] == InjectionData.AUTH_METHOD:
	    l = ['ntlm', 'basic', 'digest']
	elif event['current_val'] == InjectionData.INJECTOR:
	    l = Facade().get_injectors().names()

	return l

    # ------------------------------------------------
    # injection settings commands and parameters events
    # ------------------------------------------------
    def set_error_string(self, error_string):
	self.__logger.debug('set_error_string. START')

	if not Facade().get_session().inj_data[InjectionData.URL]:
	    self.print_msg('/* Hint: If you set url pysqlin can check error string value.')
	    return

	try:
	    db = Facade().get_session().inj_data[InjectionData.DATABASE]
	    sqli = SQLInjection(
		    Facade().get_session().inj_data,
		    Query.from_user(db),
		    None)
	    res = sqli.check_error_string(error_string)
	except MyException, e:
	    self.print_error('Warning: Error checking error string value. Please, check url value.')
	    return

	if res is None:
	    self.print_error('Error string not found in HTTP response. Please check settings.')
	elif res is InjectionData.ERROR_MODE_POSITIVE:
	    self.print_msg('Error string found in positive HTTP response.')
	    Facade().get_session().inj_data[InjectionData.ERROR_MODE] = InjectionData.ERROR_MODE_POSITIVE
	    self.print_msg('Auto setting error mode to positive.')
	elif res is InjectionData.ERROR_MODE_NEGATIVE:
	    self.print_msg('Error string found in negative HTTP response.')
	    Facade().get_session().inj_data[InjectionData.ERROR_MODE] = InjectionData.ERROR_MODE_NEGATIVE
	    self.print_msg('Auto setting error mode to negative.')

#    def evt_get_queries(self, **event):
#	self.__logger.debug('START evt_get_queries, param: %s', event)
#
#	db = Facade().get_session().inj_data[InjectionData.DATABASE]
#	l = []
#	if db: 
#	    l = Queries().get_queries_name(db)
#	    if event['cmd'] == 'show_query': l.append('all')
#
#	return l
#
#    def comm_show_queries(self, **event):
#	self.__logger.debug('START comm_show_queries, param: %s', event)
#
#    	section = event['section']
#	param = event['parameters']['injection_name']
#
#	db = Facade().get_session().inj_data[InjectionData.DATABASE]
#
#	if param == 'all':
#	    for key, query in Queries().get_queries(db):
#		self.print_msg("%s = %s" % (key,query.select))
#	else:
#	    try:
#		self.print_msg(Queries().get_query(db, param).select)
#	    except NoSectionError:
#		return "No section named %s, check configuration files" % (section)
#	    except NoOptionError:
#		return "No setting named %s" % (param)
#
#    def comm_set_queries(self, **event):
#	self.__logger.debug('START comm_set_queries, param: %s', event)
#
#    	section = event['section']
#	param, value = event['parameters'].values()
#
#	db = Facade().get_session().inj_data[InjectionData.DATABASE]
#	try:
#	    if param not in Queries().get_queries_name(db):
#		return "No injection named %s in database %s, check injections.ini file" % (param, db)
#	    Queries().set_query(db, param, value)
#	except NoSectionError:
#	    return "No database named %s, check injections.ini file" % (db)

    # ------------------------------------------------
    # injection parameters, commands and parameters events
    # ------------------------------------------------
    def evt_get_injentries(self, **event):
	self.__logger.debug('evt_get_injentries. START event=%s', event)

	l = Facade().get_session().inj_data.keys()

	if event['cmd'] == 'show': l.append('all')

	return l

    def comm_set_injentry(self, **event):
	self.__logger.debug("comm_set_injentry. event: %s", event)
	
	param = event['parameters']['parameter']
	value = event['parameters']['value']


	try:
	    # more specific functions events
	    if param == InjectionData.ERROR_STRING:
		self.set_error_string(value)
	    if param == InjectionData.INJECTOR:
		try:
		    Facade().load_injector(value)
		except KeyError:
		    raise MyException("No injector named %s" % (injector_name))
	    elif param == InjectionData.URL:
		Facade().get_session().reset_kbase()

	    Facade().get_session().inj_data[param] = value
	except KeyError:
	    return "No parameter named %s" % (param)
	except MyException, e:
           return "%s" % e

    def comm_show_injentries(self, **event):
	self.__logger.debug("comm_show_injentries. event: %s", event)

	param = event['parameters']['parameter']

	if param == 'all':
	    items2 = Facade().get_session().inj_data.items2()
	    for section in [k for k in items2.keys() if items2[k]]:
		self.print_msg("\n" + section + " options:\n")
		self.view.table_print([['Name', 'Value', 'Description']] + items2[section])
	else:
	    try:
		self.print_msg(Facade().get_session().inj_data[param])
	    except KeyError:
		return "No parameter named %s" % (param)

